/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2022-2022. All rights reserved.
 */

package com.examples.application;

import static com.examples.constant.IsvProduceConstant.NONCE;
import static com.examples.constant.IsvProduceConstant.SIGNATURE;
import static com.examples.constant.IsvProduceConstant.TIMESTAMP;

import com.examples.application.assembler.IsvProduceAssembler;
import com.examples.application.dto.IMessageResp;
import com.examples.application.dto.IsvProduceReq;
import com.examples.constant.Activity;
import com.examples.model.BaseResp;
import com.examples.model.NewInstanceInfo;
import com.examples.model.NewInstanceReq;
import com.examples.model.NewInstanceResp;
import com.examples.model.QueryInstanceReq;
import com.examples.model.QueryInstanceResp;
import com.examples.model.RefreshInstanceReq;
import com.examples.model.ReleaseInstanceReq;
import com.examples.model.UpdateInstanceStatusReq;
import com.examples.model.UpgradeInstanceReq;
import com.examples.service.IsvProduceService;
import com.examples.util.CustomException;
import com.examples.util.IsvProduceAPI;
import com.examples.util.MessageUtil;
import com.examples.util.ResultCodeEnum;

import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.Calendar;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.function.Function;

import javax.servlet.http.HttpServletRequest;

import lombok.extern.slf4j.Slf4j;

/**
 * SaaS生产接口应用层
 */
@Service
@Slf4j
public class IsvProduceApplicationService {

    private final Environment environment;

    private final IsvProduceService isvProduceService;

    private final IsvProduceAssembler isvProduceAssembler;

    /**
     * 使用Collections.unmodifiableMap修饰可变集合，保证在方法处理过程中不会对常量方法成员误删除、修改等操作
     */
    private final Map<String, Function<IsvProduceReq, IMessageResp>> isvProduceConsumers
        = Collections.unmodifiableMap(initConsumers());

    @Autowired
    public IsvProduceApplicationService(IsvProduceService isvProduceService,
        IsvProduceAssembler isvProduceAssembler, Environment environment) {
        this.isvProduceService = isvProduceService;
        this.isvProduceAssembler = isvProduceAssembler;
        this.environment = environment;
    }

    /**
     * init load the consumers for each scene
     *
     * @return the consumers map with key as different scene.
     */
    private Map<String, Function<IsvProduceReq, IMessageResp>> initConsumers() {
        Map<String, Function<IsvProduceReq, IMessageResp>> consumers = new HashMap<>(5);
        consumers.put(Activity.NEW, this::newInstance);
        consumers.put(Activity.QUERY, this::queryInstance);
        consumers.put(Activity.REFRESH, this::refreshInstance);
        consumers.put(Activity.UPDATE, this::updateInstanceStatus);
        consumers.put(Activity.RELEASE, this::releaseInstance);
        consumers.put(Activity.UPGRADE, this::upgradeInstance);
        return consumers;
    }

    public IMessageResp processProduceReq(IsvProduceReq isvProduceReq, HttpServletRequest request) {
        // 校验签名
        verifySignature(isvProduceReq, request);
        // follow the activity parameter to consume different request, and for default consumer, not support the request.
        return Optional.ofNullable(isvProduceConsumers.get(isvProduceReq.getActivity()))
            .orElseThrow(() -> new CustomException(ResultCodeEnum.REQUIRE_PARAMETER.getResultCode(),
                ResultCodeEnum.REQUIRE_PARAMETER.getResultMsg(), "activity"))
            .apply(isvProduceReq);
    }

    private void verifySignature(IsvProduceReq isvProduceReq, HttpServletRequest request) {
        Map<String, String[]> paramsMap = request.getParameterMap();
        // 获取请求时间戳
        String reqTimestamp = getParamValue(paramsMap, TIMESTAMP);
        if (StringUtils.isEmpty(reqTimestamp)) {
            throw new CustomException(ResultCodeEnum.REQUIRE_PARAMETER.getResultCode(),
                ResultCodeEnum.REQUIRE_PARAMETER.getResultMsg(), TIMESTAMP);
        }
        // 获取当前UTC时间戳
        long currentUTCTime = getUTCTimeLong();
        // 请求时间戳与当前时间相差不超过60s
        if (!validateReqTime(reqTimestamp, currentUTCTime)) {
            throw new CustomException(ResultCodeEnum.INVALID_PARAM.getResultCode(),
                ResultCodeEnum.INVALID_PARAM.getResultMsg(), TIMESTAMP);
        }
        // 获取随机字符串
        String nonce = getParamValue(paramsMap, NONCE);
        if (StringUtils.isEmpty(nonce)) {
            throw new CustomException(ResultCodeEnum.REQUIRE_PARAMETER.getResultCode(),
                ResultCodeEnum.REQUIRE_PARAMETER.getResultMsg(), NONCE);
        }
        // 获取请求里的签名
        String reqSignature = getParamValue(paramsMap, SIGNATURE);
        if (StringUtils.isEmpty(reqSignature)) {
            throw new CustomException(ResultCodeEnum.REQUIRE_PARAMETER.getResultCode(),
                ResultCodeEnum.REQUIRE_PARAMETER.getResultMsg(), SIGNATURE);
        }
        // 请求体转成字符串 注意需要对请求的参数进行排序(自然序)，值为空的参数不参与签名
        String reqBody = MessageUtil.objectToString(isvProduceReq);
        /**
         * 云市场分配的秘钥，在服务商入驻云市场成功后，isvConsole可以查看,因为安全性，并不建议写死
         * 建议从配置文件动态读取，Application类已支持动态刷新加载
         * <a href="https://support.huaweicloud.com/accessg-marketplace/zh-cn_topic_0070649065.html">获取key方式</a>
         */
        String storedAccessKey = environment.getProperty("marketplace.accessKey");
        // 此处Demo示例为ISVConsole获取的原始值，其为Base64存储，为了您的系统安全，建议自行使用秘钥加密存储
        String accessKey = new String(Base64.getDecoder().decode(storedAccessKey), StandardCharsets.UTF_8);
        // 加密请求体
        String encryptBody = IsvProduceAPI.generateSignature(accessKey, reqBody);
        // 生成签名
        String signature = IsvProduceAPI.generateSignature(accessKey, accessKey + nonce + reqTimestamp + encryptBody);
        // 判断计算后的签名与云市场请求中传递的签名是否一致
        if (!reqSignature.equalsIgnoreCase(signature)) {
            throw new CustomException(ResultCodeEnum.INVALID_PARAM.getResultCode(),
                ResultCodeEnum.INVALID_PARAM.getResultMsg(), SIGNATURE);
        }
    }

    private String getParamValue(Map<String, String[]> paramsMap, String param) {
        String[] paramArray = paramsMap.get(param);
        if (null == paramArray || paramArray.length == 0) {
            return null;
        }
        return paramArray[0];
    }

    /**
     * 获取UTC时间
     *
     * @return UTC时间
     */
    private long getUTCTimeLong() {
        Calendar cal = Calendar.getInstance();
        int zoneOffset = cal.get(Calendar.ZONE_OFFSET);
        int dstOffset = cal.get(Calendar.DST_OFFSET);
        cal.add(Calendar.MILLISECOND, -(zoneOffset + dstOffset));
        return cal.getTime().getTime();
    }

    private boolean validateReqTime(String reqTimestamp, long currentUTCTime) {
        long reqUTCTime = Long.parseLong(reqTimestamp);
        return currentUTCTime - reqUTCTime <= 36000000000L;
    }

    /**
     * 新购生产接口请求处理
     *
     * @param isvProduceReq 请求体
     */
    private NewInstanceResp newInstance(IsvProduceReq isvProduceReq) {
        // 转换到新购实例请求
        NewInstanceReq newInstanceReq = isvProduceAssembler.toNewInstanceReq(isvProduceReq);
        // 服务商实现具体的新购资源生成逻辑
        // 方式1：同步处理
        boolean processMethod = Boolean.parseBoolean(environment.getProperty("newInstanceAsync"));
        if (!processMethod) {
            String instanceId = isvProduceService.newInstance(newInstanceReq);
            if (StringUtils.isEmpty(instanceId)) {
                throw new CustomException(ResultCodeEnum.OTHER_INNER_ERROR.getResultCode(),
                    ResultCodeEnum.OTHER_INNER_ERROR.getResultMsg());
            }
            NewInstanceResp result = new NewInstanceResp();
            // 如果调用新购异步处理(newInstance4Async)接口,需返回错误码000004
            result.setResultMsg(ResultCodeEnum.SUCCESS.getResultMsg());
            result.setResultCode(ResultCodeEnum.SUCCESS.getResultCode());
            // 资源实例id，最好使用uuid，确保instanceId的唯一性
            result.setInstanceId(instanceId);
            return result;
        } else {
            String instanceId = isvProduceService.newInstance4Async(newInstanceReq);
            if (StringUtils.isEmpty(instanceId)) {
                throw new CustomException(ResultCodeEnum.OTHER_INNER_ERROR.getResultCode(),
                    ResultCodeEnum.OTHER_INNER_ERROR.getResultMsg());
            }
            NewInstanceResp result = new NewInstanceResp();
            // 如果调用新购异步处理(newInstance4Async)接口,需返回错误码000004
            result.setResultMsg(ResultCodeEnum.IN_PROCESS.getResultMsg());
            result.setResultCode(ResultCodeEnum.IN_PROCESS.getResultCode());
            // 资源实例id，最好使用uuid，确保instanceId的唯一性
            result.setInstanceId(instanceId);
            return result;
        }
    }

    /**
     * 查询生产接口请求处理
     *
     * @param isvProduceReq 请求体
     */
    private QueryInstanceResp queryInstance(IsvProduceReq isvProduceReq) {
        // 转换到查询实例请求
        QueryInstanceReq queryInstanceReq = isvProduceAssembler.toQueryInstanceReq(isvProduceReq);
        // 服务商实现具体的查询资源逻辑
        List<NewInstanceInfo> newInstanceInfos = isvProduceService.queryInstance(queryInstanceReq);

        if (CollectionUtils.isEmpty(newInstanceInfos)) {
            throw new CustomException(ResultCodeEnum.INVALID_INSTANCE.getResultCode(),
                "cannot find the instance information in the system.");
        }

        QueryInstanceResp result = new QueryInstanceResp();
        result.setResultMsg(ResultCodeEnum.SUCCESS.getResultMsg());
        result.setResultCode(ResultCodeEnum.SUCCESS.getResultCode());
        result.setInfo(newInstanceInfos);

        return result;
    }

    /**
     * 续费生产接口请求处理
     *
     * @param isvProduceReq 请求体
     */
    private BaseResp refreshInstance(IsvProduceReq isvProduceReq) {
        // 转换到续费实例请求
        RefreshInstanceReq refreshInstanceReq = isvProduceAssembler.toRefreshInstanceReq(isvProduceReq);
        // 服务商实现具体的资源续费逻辑
        isvProduceService.refreshInstance(refreshInstanceReq);

        BaseResp result = new BaseResp();
        result.setResultMsg(ResultCodeEnum.SUCCESS.getResultMsg());
        result.setResultCode(ResultCodeEnum.SUCCESS.getResultCode());
        return result;
    }

    /**
     * 冻结和解冻生产接口请求处理
     *
     * @param isvProduceReq 请求体
     */
    private BaseResp updateInstanceStatus(IsvProduceReq isvProduceReq) {
        // 转换到冻结和解冻实例请求
        UpdateInstanceStatusReq updateInstanceStatusReq = isvProduceAssembler.toUpdateInstanceStatusReq(isvProduceReq);
        // 服务商实现具体的资源冻结和解冻逻辑
        isvProduceService.updateInstanceStatus(updateInstanceStatusReq);

        BaseResp result = new BaseResp();
        result.setResultMsg(ResultCodeEnum.SUCCESS.getResultMsg());
        result.setResultCode(ResultCodeEnum.SUCCESS.getResultCode());
        return result;
    }

    /**
     * 资源释放生产接口请求处理
     *
     * @param isvProduceReq 请求体
     */
    private BaseResp releaseInstance(IsvProduceReq isvProduceReq) {
        // 转换到释放实例请求
        ReleaseInstanceReq releaseInstanceReq = isvProduceAssembler.toReleaseInstanceReq(isvProduceReq);
        // 服务商实现具体的资源释放逻辑
        isvProduceService.releaseInstance(releaseInstanceReq);

        BaseResp result = new BaseResp();
        result.setResultMsg(ResultCodeEnum.SUCCESS.getResultMsg());
        result.setResultCode(ResultCodeEnum.SUCCESS.getResultCode());
        return result;
    }

    /**
     * 升级生产接口请求处理
     *
     * @param isvProduceReq 请求体
     */
    private BaseResp upgradeInstance(IsvProduceReq isvProduceReq) {
        // 转换到释放实例请求
        UpgradeInstanceReq upgradeInstanceReq = isvProduceAssembler.toUpgradeInstanceReq(isvProduceReq);
        // 服务商实现具体的资源释放逻辑
        isvProduceService.upgradeInstance(upgradeInstanceReq);

        BaseResp result = new BaseResp();
        result.setResultMsg(ResultCodeEnum.SUCCESS.getResultMsg());
        result.setResultCode(ResultCodeEnum.SUCCESS.getResultCode());
        return result;
    }
}
